package de.sendorian.app.forumArchive.finder;


import static org.apache.commons.lang.StringEscapeUtils.unescapeHtml;
import static org.apache.commons.lang.StringUtils.trim;
import static org.apache.commons.lang.StringUtils.trimToNull;
import static org.hamcrest.Matchers.anything;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.annotation.concurrent.NotThreadSafe;

import org.apache.log4j.Logger;
import org.hamcrest.Matcher;
import org.htmlparser.Node;
import org.htmlparser.filters.TagNameFilter;
import org.htmlparser.nodes.TagNode;
import org.htmlparser.util.NodeList;

import de.sendorian.app.forumArchive.domain.Forum;
import de.sendorian.app.forumArchive.domain.Topic;
import de.sendorian.app.forumArchive.domain.User;
import de.sendorian.app.forumArchive.repository.UserRepository;

/**
 * Finds all the topics for the forum. Optionally a filter can be provided to take influence which
 * forums should be returned.
 *
 * This class is NOT safe for multithreading so be sure to create one instance for each forum in a
 * threaded environment!
 *
 * @author sendorian
 *
 */
@NotThreadSafe
public abstract class TopicFinder extends FinderBase<Forum, Topic> {
	
	private static final Logger LOG = Logger.getLogger(TopicFinder.class);

	protected Matcher<Topic> nonCriticalfilter = anything();

	protected UserRepository userRepository = new UserRepository();

	int pageNr = 0;

	boolean tryAgain = true;


	public TopicFinder() {
	}


	public TopicFinder(Matcher<Topic> filter, boolean tryAgain) {
		super(filter);
		this.tryAgain = tryAgain;
	}


	public TopicFinder(Matcher<Topic> filter, Matcher<Topic> nonCriticalfilter, boolean tryAgain) {
		super(filter);
		this.tryAgain = tryAgain;
		this.nonCriticalfilter = nonCriticalfilter;
	}


	public List<Topic> find(Forum forum) {
		pageNr = 0;
		List<Topic> topics = find(forum, forum.getUrl());
		forum.setPages(pageNr);
		LOG.info("Found " + topics.size() + " topics in '" + forum.getName() + "'");
		return topics;
	}


	public List<Topic> find(Forum forum, String url) {
		List<Topic> topics = new ArrayList<Topic>();
		NodeList tableRows = parse(url, new TagNameFilter("tr"), false);
		if (tableRows == null) {
			return topics;
		}

		pageNr++;
		LOG.info("Found topics in " + forum.getName() + ", Page " + pageNr + ":\n");
		for (Node node : tableRows.toNodeArray()) {
			Topic topic = new Topic();
			TagNode row = (TagNode) node;
			String link = getLinkWithoutAnchor(row);
			if (link != null) {
				topic.setUrl(link);
				topic.setLastPost(getLastPost(row));
				topic.setName(trim(unescapeHtml(getTopicName(row))));
				topic.setType(getType(row));
				String userName = trimToNull(unescapeHtml(getTopicStarter(row)));
				if (userName != null) {
					User user = userRepository.findByName(userName);
					if (user == null) {
						user = new User(userName);
						userRepository.save(user);
					}
					topic.setTopicStarter(user);
				}

				if (nonCriticalfilter.matches(topic)) {
					if (filter.matches(topic)) {
						topics.add(topic);
						LOG.debug("Adding " + topic.toString());
					} else {
						if (!tryAgain) {
							LOG.debug("Filter not matching any more. Returning.");
							return topics;
						}
					}
				}
			}
		}

		String nextPage = getNextPage(url, pageNr);
		if (nextPage != null) {
			List<Topic> nextPosts = find(forum, nextPage);
			topics.addAll(nextPosts);
		}

		return topics;
	}

	/**
	 * Calls {@link #getLink(TagNode)} and removes an anchor ("#anchor") from the end of the link if
	 * present.
	 */
	private String getLinkWithoutAnchor(TagNode row) {
		String link = getLink(row);
		if (link == null) {
			return null;
		}

		int anchorTagPos = link.lastIndexOf('#');
		if (anchorTagPos != -1) {
			link = link.substring(0, anchorTagPos);
		}
		return link;
	}


	protected abstract String getTopicName(TagNode row);


	protected abstract String getTopicStarter(TagNode row);


	protected abstract Date getLastPost(TagNode row);


	protected abstract String getLink(TagNode row);


	protected abstract Topic.Type getType(TagNode row);


	abstract protected String getNextPage(String url, int actualPageNr);
}
